﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.Utilities;
#if !DOT_NET
using Windows.Foundation;
using Windows.Storage;
using Windows.Storage.Streams;

#else
#endif
#if !WINDOWS_PHONE
using System.IO.Compression;

#else

#endif

namespace StyleMVVM.Utilities
{
	/// <summary>
	/// This class provides serialization helpers that user the Json Data Contract serializer.
	/// </summary>
	public static class ObjectSerializationHelper
	{
#if DOT_NET

		public static Task SerializeToFile(string fileName,
		                                   object objectValue,
		                                   CreationCollisionOption options,
		                                   IEnumerable<Type> knownTypes)
		{
			return Task.Run(
				() =>
					{
						FileMode fileMode = FileMode.Create;

						switch (options)
						{
							case CreationCollisionOption.FailIfExists:
								fileMode = FileMode.CreateNew;
								break;

							case CreationCollisionOption.GenerateUniqueName:
								fileMode = FileMode.CreateNew;

								int i = 1;
								string extension = null;

								while (File.Exists(fileName + extension))
								{
									extension = i.ToString();
									i++;
								}

								fileName = fileName + extension;

								break;

							case CreationCollisionOption.OpenIfExists:
							case CreationCollisionOption.ReplaceExisting:
								fileMode = FileMode.Create;
								break;
						}

						using (FileStream fileStream = File.Open(fileName, fileMode))
						{
							SerializeToStream(fileStream, objectValue, knownTypes);
						}
					});
		}

		public static Task<object> DeserializeFromFile(string fileName, Type deserializeType, IEnumerable<Type> knownTypes)
		{
			return Task.Run(
				() =>
					{
						using (FileStream fileStream = File.OpenRead(fileName))
						{
							return DeserializeFromStream(fileStream, deserializeType, knownTypes);
						}
					});
		}

#else

		public static IAsyncAction SerializeToFile(string fileName,
		                                           object objectValue,
		                                           CreationCollisionOption options,
		                                           IEnumerable<Type> knownTypes)
		{
			return InternalSerializeToFile(fileName, objectValue, options, knownTypes).AsAsyncAction();
		}

		internal static async Task InternalSerializeToFile(string fileName,
		                                                   object objectValue,
		                                                   CreationCollisionOption options,
		                                                   IEnumerable<Type> knownTypes)
		{
			using (MemoryStream ms = new MemoryStream())
			{
				SerializeToStream(ms, objectValue, knownTypes);

				StorageFile file =
					await ApplicationData.Current.LocalFolder.CreateFileAsync(fileName, options);

				using (Stream fileStream = await file.OpenStreamForWriteAsync())
				{
					ms.Seek(0, SeekOrigin.Begin);

					await ms.CopyToAsync(fileStream);
					await fileStream.FlushAsync();
				}
			}
		}

		public static IAsyncOperation<object> DeserializeFromFile(string fileName,
		                                                          Type deserializeType,
		                                                          IEnumerable<Type> knownTypes)
		{
			return InternalDeserializeFromFile(fileName, deserializeType, knownTypes).AsAsyncOperation();
		}

		internal static async Task<object> InternalDeserializeFromFile(string fileName,
		                                                               Type deserializeType,
		                                                               IEnumerable<Type> knownTypes)
		{
			StorageFile file = await ApplicationData.Current.LocalFolder.GetFileAsync(fileName);

			using (IInputStream inStream = await file.OpenSequentialReadAsync())
			{
				return DeserializeFromStream(inStream.AsStreamForRead(), deserializeType, knownTypes);
			}
		}
#endif

#if NETFX_CORE
		public static IAsyncAction CompressToFile(string fileName,
		                                          object objectValue,
		                                          CreationCollisionOption options,
		                                          IEnumerable<Type> knownTypes)
		{
			return InternalCompressToFile(fileName, objectValue, options, knownTypes).AsAsyncAction();
		}

		internal static async Task InternalCompressToFile(string fileName,
		                                                  object objectValue,
		                                                  CreationCollisionOption options,
		                                                  IEnumerable<Type> knownTypes)
		{
			using (MemoryStream ms = new MemoryStream())
			{
				using (GZipStream zippedStream = new GZipStream(ms, CompressionLevel.Fastest, false))
				{
					SerializeToStream(zippedStream, objectValue, knownTypes);

					StorageFile file =
						await ApplicationData.Current.LocalFolder.CreateFileAsync(fileName, options);

					using (Stream fileStream = await file.OpenStreamForWriteAsync())
					{
						ms.Seek(0, SeekOrigin.Begin);

						await ms.CopyToAsync(fileStream);
						await fileStream.FlushAsync();
					}
				}
			}
		}

		public static IAsyncOperation<object> DecompressFromFile(string fileName,
		                                                         Type compressedType,
		                                                         IEnumerable<Type> knownTypes)
		{
			return InternalDecompressFromFile(fileName, compressedType, knownTypes).AsAsyncOperation();
		}

		internal static async Task<object> InternalDecompressFromFile(string fileName,
		                                                              Type compressedType,
		                                                              IEnumerable<Type> knownTypes = null)
		{
			StorageFile file = await ApplicationData.Current.LocalFolder.GetFileAsync(fileName);

			using (IInputStream inStream = await file.OpenSequentialReadAsync())
			{
				using (GZipStream zipStream = new GZipStream(inStream.AsStreamForRead(), CompressionMode.Decompress, false))
				{
					return DeserializeFromStream(zipStream, compressedType, knownTypes);
				}
			}
		}

#elif DOT_NET

		public static async Task CompressToFile(string fileName,
		                                        object objectValue,
		                                        CreationCollisionOption options = CreationCollisionOption.OpenIfExists,
		                                        IEnumerable<Type> knownTypes = null)
		{
			using (MemoryStream ms = new MemoryStream())
			{
				using (GZipStream zippedStream = new GZipStream(ms, CompressionLevel.Fastest, false))
				{
					SerializeToStream(zippedStream, objectValue, knownTypes);

#if DOT_NET

#else
					StorageFile file =
						await ApplicationData.Current.LocalFolder.CreateFileAsync(fileName, options);

					using (Stream fileStream = await file.OpenStreamForWriteAsync())
					{
						ms.Seek(0, SeekOrigin.Begin);

						await ms.CopyToAsync(fileStream);
						await fileStream.FlushAsync();
					}
#endif
				}
			}
		}

		public static async Task<object> DecompressFromFile(string fileName,
		                                                    Type compressedType,
		                                                    IEnumerable<Type> knownTypes = null)
		{
#if DOT_NET
			// TODO
			return null;
#else
			StorageFile file = await ApplicationData.Current.LocalFolder.GetFileAsync(fileName);

			using (IInputStream inStream = await file.OpenSequentialReadAsync())
			{
				using (GZipStream zipStream = new GZipStream(inStream.AsStreamForRead(), CompressionMode.Decompress, false))
				{
					return DeserializeFromStream(zipStream, compressedType, knownTypes);
				}
			}
#endif
		}

#endif

		internal static void SerializeToStream(Stream outStream, object objectValue, IEnumerable<Type> knownTypes = null)
		{
			DataContractJsonSerializer contractSerializer = null;

			if (knownTypes != null)
			{
				contractSerializer = new DataContractJsonSerializer(objectValue.GetType(), knownTypes);
			}
			else
			{
				contractSerializer = new DataContractJsonSerializer(objectValue.GetType());
			}

			contractSerializer.WriteObject(outStream, objectValue);
		}

		internal static object DeserializeFromStream(Stream inStream, Type serializeType, IEnumerable<Type> knownTypes = null)
		{
			DataContractJsonSerializer contractSerializer = null;

			if (knownTypes != null)
			{
				contractSerializer = new DataContractJsonSerializer(serializeType, knownTypes);
			}
			else
			{
				contractSerializer = new DataContractJsonSerializer(serializeType);
			}

			return contractSerializer.ReadObject(inStream);
		}
	}
}